#pragma once
#include <common.hpp>
#include <Math/Vector.hpp>

namespace zzz{
template <unsigned int N, typename T >
class AABB
{
public:
#undef max
#undef min
  AABB():min_(numeric_limits<T>::max()),max_(-numeric_limits<T>::max()){}
  AABB(const Vector<N,T> &min,const Vector<N,T> &max):min_(min),max_(max){}
  AABB(const Vector<N,T> &p):min_(p),max_(p){}
  AABB(const vector<Vector<N,T> > &data):min_(numeric_limits<T>::max()),max_(-numeric_limits<T>::max()){
    *this += data;
  }

  // create from list of vertex
  void Reset() {
    min_=Vector<N,T>(numeric_limits<T>::max());
    max_=Vector<N,T>(-numeric_limits<T>::max());
  }

  // min and max
  Vector<N,T> &Min(){return min_;}
  const Vector<N,T> &Min() const{return min_;}
  T Min(zuint i) const {return min_[i];}
  Vector<N,T> &Max(){return max_;}
  const Vector<N,T> &Max() const{return max_;}
  T Max(zuint i) const {return max_[i];}

  // range
  Vector<2,T> Range(zuint i) const {return Vector<2,T>(Min(i), Max(i));}
  Vector<2,Vector<N,T> > Range() const {return Vector<2,Vector<N,T> >(Min(), Max());}

  // Offset
  void SetOffset(const Vector<N,T> &offset)   {
    min_+=offset;
    max_+=offset;
  }
  // Box center
  Vector<N,T> Center() const{return (min_+max_)*0.5;}
  T Center(zuint i) const{return (min_[i]+max_[i])*0.5;}

  /// Box diffrence (width/height/depth)
  Vector<N,T> Diff() const{return max_-min_;}
  T Diff(zuint i) const{return max_[i]-min_[i];}

  bool IsLegal() const {
    bool good=true;
    for (zuint i=0; i<N; i++)
      if (min_[i]>max_[i]) {
        good=false; 
        break;
      }
    return good;
  }

  bool IsEmpty() const {
    return min_==max_;
  }

  void AddData(const Vector<N,T> &p) {
    for (zuint i=0; i<N; i++) {
      if (min_[i]>p[i]) min_[i]=p[i];
      if (max_[i]<p[i]) max_[i]=p[i];
    }
  }

  template<typename T1>
  void AddData(const T1 &_begin, const T1 &_end) {
    for (T1 x=_begin; x!=_end; x++)
      AddData(*x);
  }

  /// Boolean union
  void operator+=(const Vector<N,T> &p) {
    AddData(p);
  }
  
  /// Boolean union
  void operator+=(const std::vector<Vector<N,T> > &p) {
    AddData(p.begin(), p.end());
  }
  
  /// Boolean union
  void operator+=(const AABB<N,T> &box) {
    *this+=box.min_;
    *this+=box.max_;
  }

  const AABB<N,T>& operator+(const AABB<N,T> &box) const {
    AABB<N,T> ret(*this);
    ret+=box;
    return ret;
  }

  /// Boolean intersection
  void operator*=(const AABB<N,T> &box) {
    for (zuint i=i; i<N; i++) {
      if (min_[i]<box.min_[i]) min_[i]=box.min_[i];
      if (max_[i]>box.max_[i]) max_[i]=box.max_[i];
    }
  }

  const AABB<N,T>& operator*(const AABB<N,T> &box) const {
    AABB<N,T> ret(*this);
    ret*=box;
    return ret;
  }

  /// Box equality operator
  bool operator==(const AABB<N,T> &box) const {
    return min_==box.min_ && max_==box.max_;
  }

  /// Volumetric classification
  bool IsInside(const Vector<N,T> &pos) const {
    for (zuint i=0; i<N; i++)
      if (!Within<T>(min_[i],pos[i],max_[i])) 
        return false;
    return true;
  }

  /// Intersection between boxes
  bool IsIntersect(const AABB<N,T> &box) const {
    for (zuint i=0; i<N; i++) {
      if (min_[i] > box.max_[i]) return false;
      if (max_[i] < box.min_[i]) return false;
    }
    return true;
  }

  /// Whether it intersect a sphere
  bool AABB::sphereIntersect(const Vector<N,T>& center, T r) const {
    Vector<N,T> minpos(center-r);
    Vector<N,T> maxpos(center+r);
    //the sphere's AABB do not intersect with it, it absolutly is not
    if(!IsIntersect(AABB<N,T>(minpos, maxpos)))
      return false;
    //TODO: more accurate testing
    return true;
  }

  static AABB<N,T> ZERO() {
    return AABB(Vector<N,T>(T(0)), Vector<N,T>(T(0)));
  }
  static AABB<N,T> INFINITY() {
    return AABB(Vector<N,T>(-numeric_limits<T>::max()), Vector<N,T>(numeric_limits<T>::max()));
  }

protected:
  Vector<N,T> min_, max_;
};

typedef AABB<2,zint32>     AABB2i32;
typedef AABB<2,zfloat32>   AABB2f32;
typedef AABB<2,zfloat64>   AABB2f64;
typedef AABB<3,zfloat32>   AABB3f32;
typedef AABB<3,zfloat64>   AABB3f64;
typedef AABB<4,zfloat32>   AABB4f32;
typedef AABB<4,zfloat64>   AABB4f64;

typedef AABB<2,int>     AABB2i;
typedef AABB<2,float>   AABB2f;
typedef AABB<2,double>  AABB2d;
typedef AABB<3,float>   AABB3f;
typedef AABB<3,double>  AABB3d;
typedef AABB<4,float>   AABB4f;
typedef AABB<4,double>  AABB4d;
}
